/*
 * Copyright (C) 2006-2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.Collections.Generic;
using System.IO;

using Gtk;
using Glade;
using Mono.Unix;

using DCSharp.Backend.Managers;
using DCSharp.Backend.Objects;
using DCSharp.Settings;
using DCSharp.Xml;

namespace DCSharp.Gui
{
	public class PreferencesWindow : GladeWindow
	{
		private const int DefaultDownloadSlots = 6;
		private const int DefaultUploadSlots = 6;

		private RuntimeSettings settings;
		private ConnectionSettings connectionSettings;
		private InterfaceSettings interfaceSettings;
		private ShareManager shareManager;

		private uint timeout;

		[Widget]
		private Button closeButton;

		// General

		[Widget]
		private Entry nicknameEntry;

		[Widget]
		private Entry emailEntry;

		[Widget]
		private Entry descriptionEntry;

		[Widget]
		private ComboBox connectionTypeComboBox;

		[Widget]
		private RadioButton activeConnectionButton;

		[Widget]
		private Table activeConnectionTable;

		[Widget]
		private Entry ipEntry;

		[Widget]
		private SpinButton portSpinButton;

		[Widget]
		private Button applyButton;

		// Interface
#if ENABLE_NOTIFICATIONS
		[Widget]
		private Box interfaceBox;

		[Widget]
		private Box notificationBox;

		[Widget]
		private CheckButton chatNotificationButton;

		[Widget]
		private CheckButton downloadCompleteButton;

		[Widget]
		private CheckButton allDownloadsCompleteButton;
#endif
		// Downloads

		[Widget]
		private FileChooserButton downloadDirectoryButton;

		[Widget]
		private CheckButton segmentedDownloadingButton;

		[Widget]
		private CheckButton limitDownloadsButton;

		[Widget]
		private Table limitTable;

		[Widget]
		private SpinButton downloadSlotsSpinButton;

		// Sharing

		private ListStore shareStore;

		[Widget]
		private SpinButton uploadSlotsSpinButton;

		[Widget]
		private TreeView shareTreeView;

		[Widget]
		private Button addShareButton;

		[Widget]
		private Button removeShareButton;

		[Widget]
		private Button refreshShareButton;

		[Widget]
		private Box shareSizeBox;

		[Widget]
		private Label shareSizeLabel;

		[Widget]
		private Box loadingBox;

		[Widget]
		private ProgressBar shareProgressBar;

		#region Constructors

		public PreferencesWindow(RuntimeSettings settings,
			ConnectionSettings connectionSettings,
			InterfaceSettings interfaceSettings, ShareManager shareManager,
			Window parent) : base("PreferencesWindow.glade", parent)
		{
			if (settings == null)
			{
				throw new ArgumentNullException("settings");
			}
			if (connectionSettings == null)
			{
				throw new ArgumentNullException("connectionSettings");
			}
			if (interfaceSettings == null)
			{
				throw new ArgumentNullException("interfaceSettings");
			}
			if (shareManager == null)
			{
				throw new ArgumentNullException("shareManager");
			}
			this.settings = settings;
			this.connectionSettings = connectionSettings;
			this.interfaceSettings = interfaceSettings;
			this.shareManager = shareManager;

			Window.Title = String.Format(Catalog.GetString("{0} Preferences"),
				Util.AppName);

			Initialize();
			ConnectSignals();
		}

		#endregion

		#region Methods

		public override void Destroy()
		{
			if (timeout != 0)
			{
				GLib.Source.Remove(timeout);
				timeout = 0;
			}
			base.Destroy();
		}

		private void Initialize()
		{
			// General
			LocalIdentity localUser = settings.LocalIdentity;

			nicknameEntry.Text = localUser.Nick ?? String.Empty;
			emailEntry.Text = localUser.Email ?? String.Empty;
			descriptionEntry.Text = localUser.Description ?? String.Empty;

			connectionTypeComboBox.Active = 0;

			TreeIter iter;
			TreeModel model = connectionTypeComboBox.Model;
			if (model.GetIterFirst(out iter))
			{
				do
				{
					string connection = (string)model.GetValue(iter, 0);
					if (connection == localUser.Connection)
					{
						connectionTypeComboBox.SetActiveIter(iter);
						break;
					}
				}
				while (model.IterNext(ref iter));
			}

			activeConnectionButton.Active = connectionSettings.SupportsIncoming;

			ipEntry.Text = connectionSettings.Address ?? String.Empty;
			portSpinButton.Value = connectionSettings.Port;
			activeConnectionTable.Sensitive = activeConnectionButton.Active;

			// Interface
#if ENABLE_NOTIFICATIONS
			interfaceBox.Visible = true;
			notificationBox.Visible = true;
			chatNotificationButton.Active = interfaceSettings.NotificationSettings.PrivateChat;
			downloadCompleteButton.Active = interfaceSettings.NotificationSettings.DownloadComplete;
			allDownloadsCompleteButton.Active = interfaceSettings.NotificationSettings.AllDownloadsComplete;
#endif
			// Downloads
			downloadDirectoryButton.SetCurrentFolder(settings.DownloadDirectory);

			segmentedDownloadingButton.Active = settings.SegmentedDownloading;

			limitDownloadsButton.Active = settings.DownloadSlots != null;
			limitTable.Sensitive = limitDownloadsButton.Active;
			downloadSlotsSpinButton.Value = settings.DownloadSlots ?? DefaultDownloadSlots;

			// Sharing
			shareStore = new ListStore(typeof(string), typeof(string));
			shareTreeView.Model = shareStore;

			TreeViewColumn column;
			CellRendererText renderer;

			renderer = new CellRendererText();
			renderer.Editable = true;
			renderer.Edited += OnVirtualNameEdited;
			column = shareTreeView.AppendColumn(Catalog.GetString("Name"),
				renderer, "text", 0);
			column.SortColumnId = 0;

			renderer = new CellRendererText();
			renderer.Ellipsize = Pango.EllipsizeMode.End;
			column = shareTreeView.AppendColumn(Catalog.GetString("Directory"),
				renderer, "text", 1);
			column.SortColumnId = 1;

			removeShareButton.Sensitive = false;
			uploadSlotsSpinButton.Value = settings.UploadSlots ?? DefaultUploadSlots;

			lock (shareManager.SyncRoot)
			{
				foreach (ShareInfo shared in shareManager)
				{
					shareStore.AppendValues(shared.VirtualName, shared.Path);
				}
				shareManager.DirectoryAdded += OnDirectoryAdded;
				shareManager.DirectoryRemoved += OnDirectoryRemoved;
				shareManager.DirectoryRenamed += OnDirectoryRenamed;
			}

			RefreshShare();
		}

		private void ConnectSignals()
		{
			LocalIdentity localUser = settings.LocalIdentity;

			// General
			nicknameEntry.FocusOutEvent += delegate
			{
				if (nicknameEntry.Text.Length > 0)
				{
					localUser.Nick = nicknameEntry.Text;
				}
				else
				{
					nicknameEntry.Text = localUser.Nick;
				}
			};
			emailEntry.FocusOutEvent += delegate
			{
				localUser.Email = emailEntry.Text;
			};
			descriptionEntry.FocusOutEvent += delegate
			{
				localUser.Description = descriptionEntry.Text;
			};
			connectionTypeComboBox.Changed += delegate
			{
				localUser.Connection = connectionTypeComboBox.ActiveText;
			};

			activeConnectionButton.Toggled += delegate
			{
				activeConnectionTable.Sensitive = activeConnectionButton.Active;
				UpdateSensitivity();
			};
			ipEntry.Changed += delegate
			{
				UpdateSensitivity();
			};
			portSpinButton.ValueChanged += delegate
			{
				UpdateSensitivity();
			};

			applyButton.Clicked += OnApplyButtonClicked;

			// Interface
#if ENABLE_NOTIFICATIONS
			chatNotificationButton.Toggled += delegate
			{
				interfaceSettings.NotificationSettings.PrivateChat = chatNotificationButton.Active;
			};
			downloadCompleteButton.Toggled += delegate
			{
				interfaceSettings.NotificationSettings.DownloadComplete = downloadCompleteButton.Active;
			};
			allDownloadsCompleteButton.Toggled += delegate
			{
				interfaceSettings.NotificationSettings.AllDownloadsComplete = allDownloadsCompleteButton.Active;
			};
#endif
			// Downloads
			downloadDirectoryButton.CurrentFolderChanged += delegate
			{
				settings.DownloadDirectory = downloadDirectoryButton.Filename;
				settings.TempDownloadDirectory = downloadDirectoryButton.Filename;
			};

			segmentedDownloadingButton.Toggled += delegate
			{
				settings.SegmentedDownloading = segmentedDownloadingButton.Active;
			};

			limitDownloadsButton.Toggled += delegate
			{
				settings.DownloadSlots = limitDownloadsButton.Active ?
					(int?)downloadSlotsSpinButton.ValueAsInt : null;
				limitTable.Sensitive = limitDownloadsButton.Active;
			};
			downloadSlotsSpinButton.ValueChanged += delegate
			{
				settings.DownloadSlots = downloadSlotsSpinButton.ValueAsInt;
			};

			// Sharing
			shareTreeView.Selection.Changed += OnSelectionChanged;

			addShareButton.Clicked += OnAddShareButtonClicked;
			removeShareButton.Clicked += OnRemoveShareButtonClicked;
			refreshShareButton.Clicked += OnRefreshShareButtonClicked;

			uploadSlotsSpinButton.ValueChanged += delegate
			{
				settings.UploadSlots = uploadSlotsSpinButton.ValueAsInt;
			};

			closeButton.Clicked += OnCloseButtonClicked;
		}

		private void RefreshShare()
		{
			refreshShareButton.Sensitive = shareStore.IterNChildren() > 0 &&
				!shareManager.IsLoading;

			long total = shareManager.Total;
			long remaining = shareManager.BytesRemaining;
			long hashed = total - remaining;
			bool loading = shareManager.IsLoading;

			if (loading || remaining > 0)
			{
				shareSizeBox.Visible = false;
				loadingBox.Visible = true;

				if (!loading && hashed < total && total > 0 && hashed > 0)
				{
					shareProgressBar.Fraction = (double)hashed / total;
					shareProgressBar.Text = String.Format(Catalog.GetString("{0} of {1} hashed"),
						Util.FormatFileSize(hashed), Util.FormatFileSize(total));
				}
				else
				{
					shareProgressBar.Fraction = 0;
					shareProgressBar.Text = Catalog.GetString("Loading files...");
				}
				if (timeout == 0)
				{
					timeout = GLib.Timeout.Add(500, OnRefreshTimeout);
				}
			}
			else
			{
				shareSizeBox.Visible = true;
				loadingBox.Visible = false;

				shareSizeLabel.Text = Util.FormatFileSize(total);
				if (timeout != 0)
				{
					GLib.Source.Remove(timeout);
					timeout = 0;
				}
			}
		}

		private void UpdateSensitivity()
		{
			bool active = connectionSettings.SupportsIncoming;
			if (active != activeConnectionButton.Active ||
				(active &&
				(connectionSettings.Address != ipEntry.Text ||
				connectionSettings.Port != portSpinButton.ValueAsInt)))
			{
				applyButton.Sensitive = true;
				return;
			}
			applyButton.Sensitive = false;
		}

		private void OnApplyButtonClicked(object obj, EventArgs args)
		{
			bool active = connectionSettings.SupportsIncoming;
			string ip = connectionSettings.Address;
			int port = connectionSettings.Port;

			connectionSettings.SupportsIncoming = activeConnectionButton.Active;
			connectionSettings.Address = ipEntry.Text;
			connectionSettings.Port = portSpinButton.ValueAsInt;

			if (!Globals.Runtime.StartServers())
			{
				// Restore settings
				connectionSettings.SupportsIncoming = active;
				connectionSettings.Address = ip;
				connectionSettings.Port = port;
			}
			UpdateSensitivity();
		}

		private void OnCloseButtonClicked(object obj, EventArgs args)
		{
			Destroy();
		}

		private void OnAddShareButtonClicked(object obj, EventArgs args)
		{
			FileChooserDialog chooser = new FileChooserDialog(
				Catalog.GetString("Select Folder"),
				Window, FileChooserAction.SelectFolder,
				Stock.Cancel, ResponseType.Cancel,
				Stock.Open, ResponseType.Accept);

			if (chooser.Run() == (int)ResponseType.Accept)
			{
				string name = Path.GetFileName(chooser.Filename);
				if (String.IsNullOrEmpty(name))
				{
					name = chooser.Filename;
				}
				lock (shareManager.SyncRoot)
				{
					string virtualName = name;
					int i = 2;
					while (true)
					{
						if (!shareManager.ContainsVirtualName(virtualName))
						{
							break;
						}
						virtualName = name + " " + i;
						i++;
					}
					try
					{
						shareManager.AddDirectory(chooser.Filename, virtualName);
					}
					catch
					{
						// TODO: Display error message.
					}
				}
			}
			chooser.Destroy();
		}

		private void OnRemoveShareButtonClicked(object obj, EventArgs args)
		{
			TreeIter iter;
			if (shareTreeView.Selection.GetSelected(out iter))
			{
				string path = (string)shareStore.GetValue(iter, 1);

				shareManager.RemoveDirectory(path);
			}
		}

		private void OnRefreshShareButtonClicked(object obj, EventArgs args)
		{
			shareManager.Refresh();
			RefreshShare();
		}

		private void OnVirtualNameEdited(object obj, EditedArgs args)
		{
			TreeIter iter;
			if (shareStore.GetIter(out iter, new TreePath(args.Path)))
			{
				string name = (string)shareStore.GetValue(iter, 0);
				string newName = args.NewText;

				if (newName.Length > 0)
				{
					shareManager.RenameDirectory(name, newName);
				}
			}
		}

		private void OnSelectionChanged(object obj, EventArgs args)
		{
			TreeIter iter;
			if (shareTreeView.Selection.GetSelected(out iter))
			{
				removeShareButton.Sensitive = true;
			}
			else
			{
				removeShareButton.Sensitive = false;
			}
		}

		private bool OnRefreshTimeout()
		{
			RefreshShare();
			return true;
		}

		private void OnDirectoryAdded(object obj, DirectoryEventArgs args)
		{
			Application.Invoke(delegate
			{
				shareStore.AppendValues(args.VirtualName, args.Path);

				RefreshShare();
			});
		}

		private void OnDirectoryRemoved(object obj, DirectoryEventArgs args)
		{
			Application.Invoke(delegate
			{
				TreeIter iter;
				if (GetShareIter(args.Path, out iter))
				{
					shareStore.Remove(ref iter);
					shareTreeView.ColumnsAutosize();

					RefreshShare();
				}
			});
		}

		private void OnDirectoryRenamed(object obj, DirectoryRenamedEventArgs args)
		{
			Application.Invoke(delegate
			{
				TreeIter iter;
				if (GetShareIter(args.Path, out iter))
				{
					shareStore.SetValue(iter, 0, args.VirtualName);
					shareTreeView.ColumnsAutosize();
				}
			});
		}

		private bool GetShareIter(string path, out TreeIter retIter)
		{
			TreeIter iter;
			if (shareStore.GetIterFirst(out iter))
			{
				do
				{
					string p = (string)shareStore.GetValue(iter, 1);
					if (p == path)
					{
						retIter = iter;
						return true;
					}
				}
				while (shareStore.IterNext(ref iter));
			}
			retIter = TreeIter.Zero;
			return false;
		}

		#endregion
	}
}
